/******************************************************************************
 * echo_server.c                                                               *
 *                                                                             *
 * Description: This file contains the C source code for an echo server.  The  *
 *              server runs on a hard-coded port and simply write back anything*
 *              sent to it by connected clients.  It does not support          *
 *              conflag clients.                                            *
 *                                                                             *
 * Authors: Athula Balachandran <abalacha@cs.cmu.edu>,                         *
 *          Wolf Richter <wolf@cs.cmu.edu>                                     *
 *                                                                             *
 *******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include "parse.h"

#define ECHO_PORT 9999
#define BUF_SIZE 16384
#define MAX_HEADER_BYTE 16384

int sock = -1, client_sock = -1;
char buf[BUF_SIZE];
char problems[BUF_SIZE];

int exception = 200;
const int MAX_RESPINSEBODY_BYTE = 16384;
char SERVER_NAME[20] = "liso/1.1";

int close_socket(int sock)
{
    if (close(sock))
    {
        fprintf(stderr, "Failed closing socket.\n");
        return 1;
    }
    return 0;
}
void handle_signal(const int sig)
{
    if (sock != -1)
    {
        fprintf(stderr, "\nReceived signal %d. Closing socket.\n", sig);
        close_socket(sock);
    }
    exit(0);
}
void handle_sigpipe(const int sig)
{
    if (sock != -1)
    {
        return;
    }
    exit(0);
}

char **split_requests(const char *target, int *num)
{
    char **requests = NULL;
    int count = 0;
    const char *flag = target;

    while (1)
    {
        const char *found = strstr(flag, "\r\n\r\n");

        if (found == NULL)
        {
            if (*flag != '\0')
            {
                size_t len = strlen(flag);
                char *request = (char *)malloc(len + 1);
                if (request == NULL)
                    exit(EXIT_FAILURE);
                
                memcpy(request, flag, len + 4);
                request[len + 4] = '\0';

                char **temp = (char **)realloc(requests, (count + 5) * sizeof(char *));
                if (temp == NULL)
                    exit(EXIT_FAILURE);
            
                requests = temp;
                requests[count++] = request;
            }
            break;
        }
        else
        {
            size_t len = found - flag;
            if (len > 0)
            {
                char *request = (char *)malloc(len + 1);
                if (request == NULL)
                    exit(EXIT_FAILURE);
                memcpy(request, flag, len + 4);
                request[len + 4] = '\0';

                char **temp = (char **)realloc(requests, (count + 5) * sizeof(char *));
                if (temp == NULL)
                    exit(EXIT_FAILURE);
                
                requests = temp;
                requests[count++] = request;
            }
            flag = found + 4;
        }
    }

    *num = count;
    return requests;
}

int main(int argc, char *argv[])
{
    // TODO 自己的这部分代码放进来
    /* register signal handler */
    /* process termination signals */
    signal(SIGTERM, handle_signal);
    signal(SIGINT, handle_signal);
    signal(SIGSEGV, handle_signal);
    signal(SIGABRT, handle_signal);
    signal(SIGQUIT, handle_signal);
    signal(SIGTSTP, handle_signal);
    signal(SIGFPE, handle_signal);
    signal(SIGHUP, handle_signal);
    /* normal I/O event */
    signal(SIGPIPE, handle_sigpipe);
    // ssize_t readret;
    socklen_t cli_size;
    struct sockaddr_in addr, cli_addr;

    fprintf(stdout, "----- Echo Server -----\n");

    /* all networked programs must create a socket */
    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        fprintf(stderr, "Failed creating socket.\n");
        return EXIT_FAILURE;
    }
    /* set socket SO_REUSEADDR | SO_REUSEPORT */
    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
    {
        fprintf(stderr, "Failed setting socket options.\n");
        return EXIT_FAILURE;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(ECHO_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    /* servers bind sockets to ports---notify the OS they accept connections */
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
    {
        close_socket(sock);
        fprintf(stderr, "Failed binding socket.\n");
        return EXIT_FAILURE;
    }

    if (listen(sock, 5))
    {
        close_socket(sock);
        fprintf(stderr, "Error listening on socket.\n");
        return EXIT_FAILURE;
    }

    /* finally, loop waiting for input and then write it back */
    while (1)
    {
        cli_size = sizeof(cli_addr);
        fprintf(stdout, "Waiting for connection...\n");
        client_sock = accept(sock, (struct sockaddr *)&cli_addr, &cli_size);
        if (client_sock == -1)
        {
            close(sock);
            fprintf(stderr, "Error accepting connection.\n");
            return EXIT_FAILURE;
        }
        fprintf(stdout, "New connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

        while (1)
        {
            memset(buf, 0, BUF_SIZE);
            //memset(problems, 0, BUF_SIZE);
            int readret = recv(client_sock, buf, BUF_SIZE, 0);
            if (readret < 0)
                break;
            fprintf(stdout, "Received (total %d bytes):\r\n\r\n%s\r\n", readret, buf);

            int requestAmount = 0;
            char **store;
            store = split_requests(buf, &requestAmount);

            if(requestAmount==0)
                break;

            memset(buf, 0, sizeof(BUF_SIZE));

            for (int i = 0; i < requestAmount; i++)
            {
                fprintf(stdout,"i=%d\r\n",i);

                char buffer[BUF_SIZE];
                memset(buffer,0,sizeof(buffer));

                if(!strcmp(store[i],"\r\n"))
                    continue;

                Request *request = parse(store[i], strlen(store[i]), sock);

                if (request == NULL)
                {
                    strcat(buffer, "HTTP/1.1 400 Bad request\r\n\r\n");
                    strcat(buf,buffer);
                    continue;
                }

                if (strcmp(request->http_version, "HTTP/1.1"))
                {
                    strcat(buffer, "HTTP/1.1 505 HTTP Version not supported\r\n\r\n");
                    strcat(buf,buffer);
                    continue;
                }

                if (sizeof(request->headers) > MAX_HEADER_BYTE)
                {
                    strcat(buffer, "HTTP/1.1 400 Bad request\r\n\r\n");
                    strcat(buf,buffer);
                    continue;
                }

                int isImplement = 1;
                if (!strcmp(request->http_method, "GET") || !strcmp(request->http_method, "HEAD"))
                {
                    char url[100] = "./static_site/";
                    if (!strcmp(request->http_uri, "/"))
                        strcat(url, "index.html");
                    else
                        strcat(url, request->http_uri);

                    struct stat stat_buf;
                    if (stat(url, &stat_buf) == -1)
                    {
                        strcat(buffer, "HTTP/1.1 404 Not Found\r\n\r\n");
                        strcat(buf,buffer);
                        continue;
                    }

                    strcat(buffer, "HTTP/1.1 200 OK");
                    strcat(buffer, "\r\nServer: ");
                    strcat(buffer, SERVER_NAME);

                    strcat(buffer, "\r\nDate: ");
                    time_t now_date;
                    struct tm *now_info;
                    char date_buf[35];
                    time(&now_date);
                    now_info = localtime(&now_date);
                    strftime(date_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", now_info);
                    strcat(buffer, date_buf);

                    strcat(buffer, "\r\nContent-Length: ");
                    char len_buf[10];
                    sprintf(len_buf, "%ld", stat_buf.st_size);
                    strcat(buffer, len_buf);

                    strcat(buffer, "\r\nContent-type: ");
                    char ext[10] = "";
                    int n = sizeof(url) / sizeof(url[0]);
                    int i;
                    for (i = n - 1; i >= 0; i--)
                    {
                        if (url[i] == '.')
                            break;
                    }
                    strcpy(ext, url + i + 1);
                    char file_type[20];
                    memset(file_type, 0, sizeof(file_type));
                    if (!strcmp(ext, "html") || !strcmp(ext, "css"))
                        strcat(file_type, "text/");
                    else if (!strcmp(ext, "png") || !strcmp(ext, "jpeg") || !strcmp(ext, "gif"))
                        strcat(file_type, "image/");
                    strcat(file_type, ext);
                    strcat(buffer, file_type);

                    strcat(buffer, "\r\nLast-modified: ");
                    time_t modi_date = stat_buf.st_mtime;
                    struct tm *modi_info;
                    char modi_buf[35];
                    time(&modi_date);
                    modi_info = gmtime(&modi_date);
                    strftime(modi_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", modi_info);
                    strcat(buffer, modi_buf);

                    strcat(buffer, "\r\nConnection: keep-alive\r\n\r\n");

                    if (!strcmp(request->http_method, "GET"))
                    {
                        char msg[MAX_RESPINSEBODY_BYTE];
                        memset(msg, 0, MAX_RESPINSEBODY_BYTE);
                        const int fd_in = open(url, O_RDONLY);
                        if (fd_in < 0)
                        {
                            memset(buffer, 0, BUF_SIZE);
                            strcat(buffer, "HTTP/1.1 404 Not Found\r\n\r\n");
                            strcat(buf,buffer);
                            continue;
                        }

                        int readRet = (int)read(fd_in, msg, BUFSIZ);
                        strcat(buffer, msg);
                        strcat(buffer,"\r\n\r\n");
                    }

                    strcat(buf,buffer);
                }

                else if (!strcmp(request->http_method, "POST"))
                {
                    time_t now_date;
                    struct tm *now_info;
                    char date_buf[35];
                    time(&now_date);
                    now_info = localtime(&now_date);
                    strftime(date_buf, 80, "%a, %d %b %Y %H:%M:%S GMT", now_info);
                }
                else
                    isImplement = 0;

                if (!isImplement)
                {
                    memset(buffer, 0, BUF_SIZE);
                    strcat(buffer, "HTTP/1.1 501 Not Implemented\r\n\r\n");
                    strcat(buf,buffer);
                }

                free(request);
            }


            if (send(client_sock, buf, strlen(buf), 0) < 0)
            {
                fprintf(stdout, "Send back failed\r\n");
                break;
            }
        }

        if (close_socket(client_sock))
        {
            close_socket(sock);
            fprintf(stderr, "Error closing client socket.\n");
            return EXIT_FAILURE;
        }
        fprintf(stdout, "Closed connection from %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
    }

    close_socket(sock);

    return EXIT_SUCCESS;
}